/*
	Copyright (C) 2010 Salil Bhagurkar

	This file is part of k3

	k3 is free software: you can redistribute it and/or modify
	it under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	k3 is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with k3.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <shared.h>

/**
 * This file contains the main fs functions
 */


static struct fs *parse_path_name(struct fs *from, const char *s);

struct fs root_fs = {.name = "", FS_INIT};

struct fs_system *fs_system_generic = NULL;
struct fs_system *fs_system_dummy = NULL;

/**
 * Read data from the node 'this' to the buffer 'buf'
 * This function applies for all data nodes or nodes
 * with no system specified
 */
int fs_system_generic_read(struct fs *referrer, struct fs *this, struct buffer *buf, u32 offset, u32 count)
{
	D_CHECK_NULL(this, "this");
	D_CHECK_NULL(buf, "buf");
	dprint(D_INFO, this->name, "");
	buffer_read(&this->buf, buf, offset, count);
	return 0;
}

/**
 * Generic write for 'data' member in fs structure
 * Performs allocation if data write is beyond available limit
 */
int fs_system_generic_write(struct fs *referrer, struct fs *this, struct buffer *buf, u32 offset, u32 count)
{
	D_CHECK_NULL(this, "this");
	D_CHECK_NULL(buf, "buf");
	dprint(D_INFO, this->name, "");
	buffer_write(&this->buf, buf, offset, count);
	return 0;
}

/**
 * Generic rename function for a node
 */
int fs_system_generic_rename(struct fs *referrer, struct fs *this, char *new_name)
{
	D_CHECK_NULL(this, "this");
	dprint(D_INFO, this->name, "");
	string_copy(this->name, new_name, FS_NAME_LEN);
	//Null terminate always
	this->name[FS_NAME_LEN - 1] = '\0';
	return 0;
}

/**
 * Transfer data while copying a node. This permits constants
 * to be written as otherwise, you won't be able to copy them.
 */
int fs_transfer_data(struct fs *fs1, struct fs *fs0)
{
	dprint(D_INFO, "", "%s to %s", fs0->name, fs1->name);
	/*Temporary buffer to transfer data*/
	struct buffer *buffer = buffer_create(0);

	//Perform read on the source node
	fs_read(fs1, fs0, buffer, 0, fs0->buf.len);
	if(buffer->len == 0) {
		dprint(D_ALERT, fs0->name, "Length is zero post read");
	}
	//Now use the read amount (indicated in the buffer structure)
	//to write back to the destination node
	fs_write(fs0, fs1, buffer, 0, buffer->len);

	buffer_free(buffer);
	return 0;
}


/**
Move an fs node from one parent to another
*/
int fs_move(struct fs *reparent, struct fs *from)
{
	int err;
	dprint(D_INFO, "", "%s to %s", from->name, reparent->name);
	D_CHECK_NULL(reparent, "reparent");
	D_CHECK_NULL(from, "from");
	err = fs_unlink(from);
	if(err) {
		dprint(D_ALERT, "from", "Unlink failure");
		return err;
	}
	err = fs_link(reparent, from);
	return err;
}

/**
Copy an fs node from one parent to another
Procedure:
Memcpy the entire node
Duplicate data
System pointer will remain same as that will be calling the same functions of the subsystem.
Resource parameters will also remain the same
*/
int fs_copy_get_new(struct fs *reparent, struct fs *from, struct fs **new_node)
{
	int err;
	dprint(D_INFO, "", "%s to %s", from->name, reparent->name);
	struct fs *n;
	D_CHECK_NULL(reparent, "reparent");
	D_CHECK_NULL(from, "from");
	n = (struct fs *)malloc_get(sizeof(struct fs), "fscopy");

	memcpy(n, from, sizeof(struct fs)); //replicate
	n->next = n->prev = n->parent = n->links = n->linkstail = NULL;

	//Perform a transfer in case of data or const, not otherwise..
	//Verify this rule.
	if((from->flags & FSFLAGS_TYPE_MASK) == FSFLAGS_DATA) {
		//Remove old data info from the destination
		n->buf.data = NULL;
		n->buf.len = 0;
		n->buf.alloc_len = 0;

		fs_transfer_data(n, from);
	} else {
		dprint(D_INFO, "from", "Skip transfer");
	}

	err = fs_link(reparent, n);

	*new_node = n; //Pointer to the node that was created
	return err;
}

/**
 * Simple copy to copy one node from here to there
 */
int fs_copy(struct fs *reparent, struct fs *from)
{
	struct fs *dum;
	return fs_copy_get_new(reparent, from, &dum);
}

/**
 * Copy and recurse to copy children too
 */
int fs_copy_recurse(struct fs *reparent, struct fs *from)
{
	struct fs *new_node, *t;
	int e;
	dprint(D_INFO, "", "%s to %s", from->name, reparent->name);
	int err = fs_copy_get_new(reparent, from, &new_node);
	if(err) {
		dprint(D_ALERT, from->name, "Error while copying");
		return err;
	}
	//Now copy all links of from to the new node
	t = from;
	e = fsiter_get_child(&t);
	while(!e) {
		err = fs_copy_recurse(new_node, t); //Copy the child to the new node
		if(err) {
			dprint(D_ALERT, t->name, "Error while copying");
			return err;
		}
		e = fsiter_get_next(&t);
	}
	return 0;
}



/**
Basic function to Link an fs node into an fs node
*/
int fs_link(struct fs *x, struct fs *link)
{
	dprint(D_INFO, x->name, link->name);
	D_CHECK_NULL(x, "x");
	D_CHECK_NULL(link, "link");
	if(LIST_PRESENT(x->links, link)) {
		dprint(D_ALERT, link->name, "Already present in list");
		return ESTATEINV;
	}
	LIST_ATTACH(x->links, x->linkstail, link);
	link->parent = x;
	int err = fsres_child_listeners(x, link);
	if(err) {
		dprint(D_ALERT, x->name, "Child listeners error");
		return err;
	}
	return 0;
}

/**
Unlink a node from the fs
*/
int fs_unlink(struct fs *link)
{
	dprint(D_INFO, "", link->name);
	D_CHECK_NULL(link, "link");
	struct fs *x = link->parent;
	D_CHECK_NULL(x, "x");
	if(!LIST_PRESENT(x->links, link)) {
		dprint(D_ALERT, link->name, "Already present in list");
		return ESTATEINV;
	}
	LIST_DETACH(x->links, x->linkstail, link);
	link->parent = NULL;
	return 0;
}


/**
 * Unlink all children and their children and their children
 * and their children and their children...
 * The first function build_list builds a list of all nodes
 * to be unlinked
 * Then all nodes are unlinked
 */
struct node_list {
	struct fs *node;
	struct node_list *next, *prev;
};
static void build_list(struct node_list **nodes, struct node_list **nodestail, struct fs *fs)
{
	struct node_list *n;
	dprint(D_PROG, fs->name, "");
	n = (struct node_list *)malloc_get(sizeof(struct node_list), "nodelst");
	n->node = fs;
	n->next = n->prev = NULL;
	LIST_ATTACH((*nodes), (*nodestail), n);
	int err = fsiter_get_child(&fs);
	while(!err) {
		build_list(nodes, nodestail, fs);
		err = fsiter_get_next(&fs);
	}
}
int fs_unlink_recurse(struct fs *first_node)
{
	dprint(D_INFO, first_node->name, "");
	int err;
	struct node_list *nodes = NULL, *nodestail = NULL;
	build_list(&nodes, &nodestail, first_node);
	struct node_list *t;
	LIST_FOR(nodes, t) {
		err = fs_unlink(t->node);
		//If there was an error unlinking at any point, stop
		if(err) {
			dprint(D_ALERT, t->node->name, "Unlink error");
			LIST_DESTROY(nodes, nodestail);
			return err;
		}
	}
	LIST_DESTROY(nodes, nodestail);
	return 0;
}

/**
Create a new fs system for an fs node and initialize it with methods
*/
struct fs_system *fs_create_system(u32 flags,
	int (*read)(struct fs *referrer, struct fs *this, struct buffer *buf, u32 offset, u32 count),
	int (*write)(struct fs *referrer, struct fs *this, struct buffer *buf, u32 offset, u32 count),
	int (*rename)(struct fs *referrer, struct fs *this, char *new_name), struct fs *fs)
{
	dprint(D_INFO, fs->name, "");
	if(!read || !write || !rename) {
		dprint(D_ALERT, "", "Assigning NULL method");
	}
	struct fs_system *system = (struct fs_system *)malloc_get(sizeof(struct fs_system), "fssys");
	system->flags = flags;
	INIT_FS_SYSTEM_RESOURCE(system);
	INIT_FS_SYSTEM_EVENT(system);
	system->read = read;
	system->write = write;
	system->rename = rename;
	system->fs = fs;
	return system;
}


/**
Create an fs node, and initialize it with the name specified.
*/
struct fs *fs_create(u32 flags, char *name)
{
	dprint(D_INFO, name, "flags = %u", flags);
	struct fs *n;
	n = (struct fs *)malloc_get(sizeof(struct fs), "fsc");
	INIT_FS(n);
	n->system = fs_system_generic;
	n->flags = flags;
	string_copy(n->name, name, FS_NAME_LEN);
	//Ensure null terminator
	n->name[FS_NAME_LEN - 1] = '\0';
	return n;
}

/**
Rename an fs node by calling it's respective rename method
*/
int fs_rename(struct fs *referrer, struct fs *x, char *new_name)
{
	dprint(D_INFO, x->name, new_name);
	D_CHECK_NULL(x->system, "x->system");
	D_CHECK_NULL(x->system->rename, "x->system->rename");
	return x->system->rename(referrer, x, new_name);
}


/**
Perform a read operation on the file system node
*/
int fs_read(struct fs *referrer, struct fs *x, struct buffer *buf, u32 offset, u32 count)
{
	dprint(D_FREQ, x->name, "");
	int err;
	D_CHECK_NULL(x, "x");
	D_CHECK_NULL(x->system, "x->system");
	D_CHECK_NULL(x->system->read, "x->system->read");
	err = fsres_rw_enumerators(x, RW_R); //Call sisters
	if(err) {
		dprint(D_ALERT, x->name, "rw enumerators fail");
		return err;
	}
	if(x->system->flags & SYSTEM_RESOURCE_WAIT_READ) {
		while(offset > x->system->read_count || (offset + count) > x->system->read_count)
			fsres_wait_on_read(x, referrer);
	}
	err = x->system->read(referrer, x, buf, offset, count);
	if(!err) {
		if(x->system->flags & SYSTEM_RESOURCE_WAIT_READ) { //Decrement resource
			x->system->read_count -= count;
		}
	}
	return err;
}

/**
Perform the write operation on the file system node
*/
int fs_write(struct fs *referrer, struct fs *x, struct buffer *buf, u32 offset, u32 count)
{
	dprint(D_FREQ, x->name, "");
	int err;
	D_CHECK_NULL(x, "x");
	D_CHECK_NULL(x->system, "x->system");
	D_CHECK_NULL(x->system->read, "x->system->read");
	err = fsres_rw_enumerators(x, RW_W); //Call sisters
	if(err) {
		dprint(D_ALERT, x->name, "rw enumerators fail");
		return err;
	}
	if(x->system->flags & SYSTEM_RESOURCE_WAIT_WRITE) {
		while(offset > x->system->write_count || (offset + count) > x->system->write_count)
			fsres_wait_on_write(x, referrer);
	}
	err = x->system->write(referrer, x, buf, offset, count);
	if(!err) {
		if(x->system->flags & SYSTEM_RESOURCE_WAIT_WRITE) {
			x->system->write_count -= count;
		}
	}
	return err;
}



int fs_init()
{
	struct fs *test;
	dprint(D_PROG, "fs", "initiate");
	fs_system_generic = fs_create_system(0, fs_system_generic_read, fs_system_generic_write, fs_system_generic_rename, NULL);
	fs_system_dummy = fs_create_system(0, dummy, dummy, fs_system_generic_rename, NULL);
	root_fs.system = fs_system_generic;
	return 0;
}
